spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { notFound } from 'next/navigation';4import { DiffViewer } from '@/components/events/diff-viewer';5import { EventList } from '@/components/events/event-row';6import { SignificanceBadge } from '@/components/ui/badges';7import { KV, Row } from '@/components/ui/key-value';8import { Container, Note } from '@/components/ui/section';9import { api, ApiError } from '@/lib/api';10import { fmtDateTime, fmtInt, fmtPct } from '@/lib/format';11import { routes, SURFACE_LABELS } from '@/lib/site';1213export const revalidate = 600;14export const metadata: Metadata = { title: 'Change', robots: { index: false } };1516export default async function ChangePage({ params }: { params: Promise<{ id: string }> }) {17 const { id } = await params;18 let ch;19 try {20 ch = await api.change(id);21 } catch (e) {22 if (e instanceof ApiError && e.notFound) notFound();23 throw e;24 }25 return (26 <Container>27 <div className="pb-4 pt-6 md:pt-10">28 <p className="eyebrow flex flex-wrap items-center gap-2">29 Change · {SURFACE_LABELS[ch.surface] ?? ch.surface}30 <SignificanceBadge value={ch.significance} />31 </p>32 <h1 className="display mt-2 text-[24px] md:text-[32px]">33 {ch.company ? (34 <Link href={routes.company(ch.company.slug)} className="hover:text-accent">35 {ch.company.display_name}36 </Link>37 ) : (38 'Monitored page'39 )}{' '}40 <span className="text-ink-3">·</span> {SURFACE_LABELS[ch.surface] ?? ch.surface} changed41 </h1>42 <p className="mt-2 text-sm text-ink-2">43 Detected {fmtDateTime(ch.detected_at)} · kind <span className="mono">{ch.kind}</span> · text delta {fmtPct(ch.text_delta_ratio, 1, true)}44 {ch.similarity !== null ? ` · similarity ${fmtPct(ch.similarity, 1, true)}` : ''}45 </p>46 </div>47 <div className="grid gap-8 lg:grid-cols-12">48 <div className="lg:col-span-8">49 <DiffViewer diff={ch.diff} significance={ch.significance} />50 </div>51 <aside className="space-y-6 lg:col-span-4">52 <KV>53 <Row k="Sensor">54 <Link href={routes.sensor(ch.sensor_id)} className="link mono text-xs break-all">55 {ch.sensor_id}56 </Link>57 </Row>58 <Row k="Before">59 {ch.snapshot_before ? (60 <Link href={routes.snapshot(ch.snapshot_before)} className="link mono text-xs break-all">61 {ch.snapshot_before}62 </Link>63 ) : (64 <span className="text-ink-3">first observation</span>65 )}66 </Row>67 <Row k="After">68 <Link href={routes.snapshot(ch.snapshot_after)} className="link mono text-xs break-all">69 {ch.snapshot_after}70 </Link>71 </Row>72 {ch.snapshot_before && (73 <Row k="Full diff">74 <Link href={routes.snapshotDiff(ch.snapshot_before, ch.snapshot_after)} className="link text-xs">75 Snapshot diff viewer →76 </Link>77 </Row>78 )}79 <Row k="Blocks">80 <span className="tnum">81 +{fmtInt(ch.blocks_added)} · −{fmtInt(ch.blocks_removed)} · ~{fmtInt(ch.blocks_modified)}82 </span>83 </Row>84 </KV>85 {Object.keys(ch.structured_delta ?? {}).length > 0 && (86 <div>87 <p className="eyebrow mb-1">Structured delta</p>88 <pre className="overflow-x-auto border border-rule bg-surface-2 p-3 text-xs">{JSON.stringify(ch.structured_delta, null, 2)}</pre>89 </div>90 )}91 <div>92 <p className="eyebrow mb-1">Events derived from this change</p>93 <EventList events={ch.events ?? []} variant="table" showCompany={false} emptyLabel="No structured event was derived from this change (below the significance threshold or awaiting processing)." />94 </div>95 <Note>Raw observations, normalised snapshots, diffs and events are stored separately; this diff can be recomputed from the two snapshots without re-fetching the page.</Note>96 </aside>97 </div>98 </Container>99 );100}101